package wireguard

import (
	"fmt"
	"golang.zx2c4.com/wireguard/conn"
	"golang.zx2c4.com/wireguard/device"
	"golang.zx2c4.com/wireguard/tun"
	"log"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"strconv"
	"strings"
	"syscall"
)

func main() {
	errs := make(chan error)
	term := make(chan os.Signal, 1)

	// if runtime.GOOS != "" { // "darwin", "windows"
	// 	fmt.Println(runtime.GOOS)
	// 	return
	// }

	// for macos interface name must be utun0..9
	interfaceName, err := getNextInterfaceName()
	if err != nil {
		log.Fatalf("Failed to get next interface name: %v", err)
	}

	logger := device.NewLogger(device.LogLevelVerbose, fmt.Sprintf("(%s) ", interfaceName))

	// create utun tunnel and device, after this two commands ifconfig will show our interface
	t, err := tun.CreateTUN(interfaceName, device.DefaultMTU)
	if err != nil {
		log.Fatalf("Failed to create TUN: %v", err)
	}
	defer t.Close()

	dev := device.NewDevice(t, conn.NewDefaultBind(), logger)

	// talk to interface, we are sending commands to configure it, note that it is little bit different than configs we are used to
	// private_key=$(cat peer1.privatekey | base64 -d | xxd -p -c32)
	// public_key=$(cat server.publickey | base64 -d | xxd -p -c32)
	dev.IpcSet(`private_key=OLnsjZvv71OBeLsu+8qkkGZTj+rt6oAw4uxd5JYg1mw=
public_key=p2ymndzXWeyrTo3M4rFHfykDMhVjjGL4Vauiezd8IWI=
endpoint=60.204.226.164:49000
allowed_ip=172.16.20.1/32
persistent_keepalive_interval=25
`)
	err = dev.Up() // gets up automatically in macos, but not in windows
	if err != nil {
		log.Fatalf("Failed to up: %v", err)
	}
	defer dev.Close()
	// at this point connection is established and because of keepalive we will see in the logs that packets are sending

	// TODO: for windows something like:
	// netsh interface ipv4 show config
	// netsh interface ipv4 set address name="utun1" static 10.14.14.2 255.0.0.0
	// route print
	// route -p add 10.0.0.0 MASK 255.0.0.0 10.14.14.2

	// additional changes we need: configure ip address of created utun interface, add row to an routing table, configure search names and dns

	// configure IP address
	_, err = exec.Command("ifconfig", interfaceName, "inet", "10.14.14.2", "10.14.14.2", "alias").Output()
	if err != nil {
		log.Fatalf("Failed to set IP address: %v", err)
	}

	// add routes
	_, err = exec.Command("route", "-q", "-n", "add", "-inet", "10.0.0.0/8", "-interface", interfaceName).Output()
	if err != nil {
		log.Fatalf("Failed to add route: %v", err)
	}

	// configure DNS
	_, err = exec.Command("networksetup", "-setdnsservers", "Wi-Fi", "10.0.0.10").Output()
	if err != nil {
		log.Fatalf("Failed to add route: %v", err)
	}

	// configure search domains
	_, err = exec.Command("networksetup", "-setsearchdomains", "Wi-Fi", "dev.svc.cluster.local svc.cluster.local cluster.local").Output()
	if err != nil {
		log.Fatalf("Failed to add route: %v", err)
	}

	// wait for program to terminate

	signal.Notify(term, syscall.SIGTERM)
	signal.Notify(term, os.Interrupt)

	select {
	case <-term:
	case <-errs:
	case <-dev.Wait():
	}

	logger.Verbosef("\nShutting down")

	// ip address and routes are removed automatically

	// remove dns
	_, err = exec.Command("networksetup", "-setdnsservers", "Wi-Fi", "empty").Output()
	if err != nil {
		log.Fatalf("Failed to add route: %v", err)
	}

	// remove searchdomains
	_, err = exec.Command("networksetup", "-setsearchdomains", "Wi-Fi", "empty").Output()
	if err != nil {
		log.Fatalf("Failed to add route: %v", err)
	}
}

// instead of this function you may just hardcode interface name
func getNextInterfaceName() (string, error) {
	ifaces, err := net.Interfaces()
	if err != nil {
		return "", err
	}

	num := 0
	for _, iface := range ifaces {
		if strings.HasPrefix(iface.Name, "utun") {
			idx, err := strconv.Atoi(strings.ReplaceAll(iface.Name, "utun", ""))
			if err != nil {
				continue
			}
			if idx > num {
				num = idx
			}

		}
	}
	return fmt.Sprintf("utun%d", num+1), nil
}
